﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using System.Text;
using System.Reflection;
using Djs.Neuronic.Core;
using Djs.Neuronic.Medium;

namespace Djs.Neuronic.Brain
{

    #region DeclarationsCls : Soubor deklarací
    public partial class DeclarationsCls : CommonCls
    {
        #region On-Demand property DatabaseDeclaration
        /// <summary>
        /// Deklarace objektů databáze načtená z XML dokumentu
        /// </summary>
        public DatabaseDeclarationCls DatabaseDeclaration
        {
            get
            {
                if (this._DatabaseDeclaration == null)
                    this._DatabaseDeclaration = DatabaseDeclarationCls.Create(this.Creator);
                return this._DatabaseDeclaration;
            }
        }
        private DatabaseDeclarationCls _CreateDatabaseDeclaration()
        {
            throw new NotImplementedException();
        }
        private DatabaseDeclarationCls _DatabaseDeclaration;
        #endregion
        #region On-Demand property ClassDeclaration
        /// <summary>
        /// Deklarace datových tříd načtená z XML dokumentu
        /// </summary>
        public ClassDeclarationCls ClassDeclaration
        {
            get
            {
                if (this._ClassDeclaration == null)
                    this._ClassDeclaration = ClassDeclarationCls.Create(this.Creator);
                return this._ClassDeclaration;
            }
        }
        private ClassDeclarationCls _CreateClassDeclaration()
        {
            throw new NotImplementedException();
        }
        private ClassDeclarationCls _ClassDeclaration;
        #endregion
        #region SelfTest
        internal override void SelfTest()
        {
            TestCreateObjects();
        }
        private void TestCreateObjects()
        {
            ClassInfo wordInfo = this.ClassDeclaration.GetClassInfo(typeof(WordCls));
            using (var time = this.Creator.GetTimer())
            {
                int cnt = 250000;
                decimal div = (decimal)cnt / 1000m;
                DateTime now = DateTime.Now;

                int x = 0;
                for (int i = 0; i < cnt; i++)
                    x = i;
                decimal tm0 = time.ElapsedMiliSeconds / div;     // mikrosekund / režie 1 cyklus

                time.Reset();
                Djs.Neuronic.Brain.WordCls wc1;
                for (int i = 0; i < cnt; i++)
                    wc1 = new WordCls();
                decimal tm1 = time.ElapsedMiliSeconds / div;     // mikrosekund / 1 objekt

                time.Reset();
                object wc2;
                for (int i = 0; i < cnt; i++)
                    wc2 = wordInfo.CreateInstance();
                decimal tm2 = time.ElapsedMiliSeconds / div;     // mikrosekund / 1 objekt

                time.Reset();
                Djs.Neuronic.Brain.WordCls wc3;
                for (int i = 0; i < cnt; i++)
                    wc3 = wordInfo.CreateTypeInstance<WordCls>();
                decimal tm3 = time.ElapsedMiliSeconds / div;     // mikrosekund / 1 objekt

                TimeSpan ts = DateTime.Now - now;
                System.Windows.Forms.MessageBox.Show(
                    "new WordCls() = " + (tm1 - tm0).ToString("# ##0.00").Trim() + " mikrosec / 1 objekt." + Environment.NewLine +
                    "Activator() = " + (tm2 - tm0).ToString("# ##0.00").Trim() + " mikrosec / 1 objekt." + Environment.NewLine +
                    "Activator() as type = " + (tm3 - tm0).ToString("# ##0.00").Trim() + " mikrosec / 1 objekt." + Environment.NewLine +
                    "Režie (už je odečtená) = " + tm0.ToString("# ##0.00").Trim() + " mikrosec / 1 objekt." + Environment.NewLine +
                    "Čas celkem = " + ts.TotalSeconds.ToString("# ##0.000").Trim() + " sec; Počet objektů = " + (3 * cnt).ToString());
            }
        }

        #endregion
    }
    #endregion
    #region DatabaseDeclarationCls : Deklarace databáze, načtená z XML souboru
    /// <summary>
    /// Třída, která umožňuje načítání položek z deklarace databáze (Data/DatabaseDeclaration.xml) do objektů DbElement a další,
    /// které následně umožní porovnat požadavek s realitou a případně doskriptovat rozdíly.
    /// </summary>
    public class DatabaseDeclarationCls
    {
        #region Konstrukce, public property
        private DatabaseDeclarationCls(CreatorCls creator)
        {
            this._Creator = creator;
            this.DbDatabase = null;
            this.Schemas = new List<DbElementSchema>();
            this.Tables = new List<DbElementTable>();
        }
        public static DatabaseDeclarationCls Create(CreatorCls creator)
        {
            DatabaseDeclarationCls declaration = new DatabaseDeclarationCls(creator);
            try
            {
                string fileName = creator.SearchDataFile(FILE_NAME);
                if (fileName != null)
                    declaration.LoadFromFile(fileName);
            }
            catch (Exception)
            { }
            return declaration;
        }
        public const string FILE_NAME = "DatabaseDeclaration.xml";
        /// <summary>
        /// Reference na objekt Database, který zajistí přístup k SQL serveru
        /// </summary>
        private CreatorCls _Creator { get; set; }
        /// <summary>
        /// Reference na objekt Database, který zajistí přístup k SQL serveru
        /// </summary>
        private DatabaseCls _Database { get { return this._Creator.Database; } }
        /// <summary>
        /// Vlastnosti databáze načtené z XML deklarace
        /// </summary>
        public DbElementDatabase DbDatabase { get; private set; }
        /// <summary>
        /// Potřebná schemata načtená z XML deklarace
        /// </summary>
        public List<DbElementSchema> Schemas { get; private set; }
        /// <summary>
        /// Potřebné tabulky načtené z XML deklarace
        /// </summary>
        public List<DbElementTable> Tables { get; private set; }
        #endregion
        #region Načítání z XML souboru
        /// <summary>
        /// Načte obsah daného souboru do this
        /// </summary>
        /// <param name="fileName"></param>
        public void LoadFromFile(string fileName)
        {
            XDocument xDoc = XDocument.Load(fileName);
            if (xDoc.Root != null && xDoc.Root.Name.LocalName == "database")
            {
                foreach (XElement element in xDoc.Root.Elements())
                    this.AddItemFrom(element);
            }
        }
        /// <summary>
        /// Přidá do sebe další prvek z XML elementu.
        /// Automaticky detekuje jeho typ podle názvu elementu.
        /// </summary>
        /// <param name="element"></param>
        private void AddItemFrom(XElement element)
        {
            switch (element.Name.LocalName)
            {
                case "db_properties":
                    this.FillDbProperties(element);
                    break;
                case "db_schema":
                    this.AddSchemaFromXml(element);
                    break;
                case "table":
                    this.AddTableFromXml(element);
                    break;
            }
        }
        /// <summary>
        /// Načte vlastnosti databáze z dodaného XML elementu.
        /// </summary>
        /// <param name="element"></param>
        private void FillDbProperties(XElement element)
        {
            this.DbDatabase = DbElementDatabase.CreateFromXml(element);
        }
        /// <summary>
        /// Přidá do sebe schema popsané dodaným XML elementem.
        /// </summary>
        /// <param name="element"></param>
        private void AddSchemaFromXml(XElement element)
        {
            DbElementSchema schema = DbElementSchema.CreateFromXml(element);
            if (schema != null)
                this.Schemas.Add(schema);
        }
        /// <summary>
        /// Přidá do sebe tabulku popsanou dodaným XML elementem. Včetně sloupců a indexů.
        /// </summary>
        /// <param name="element"></param>
        private void AddTableFromXml(XElement element)
        {
            DbElementTable table = DbElementTable.CreateFromXml(element);
            if (table != null)
                this.Tables.Add(table);
        }
        #endregion
    }
    #endregion
    #region ClassDeclarationCls : umožňuje načítání položek z deklarace logické vrstvy (Data/ClassDeclaration.xml) do objektů ClassInfo a další
    /// <summary>
    /// ClassDeclarationCls : Třída, která umožňuje načítání položek z deklarace logické vrstvy (Data/ClassDeclaration.xml) do objektů ClassInfo a další,
    /// které následně umožní pracovat s definicí dat.
    /// </summary>
    public class ClassDeclarationCls
    {
        #region Konstrukce, public property
        public ClassDeclarationCls(CreatorCls creator)
        {
            this.Creator = creator;
            this.Classes = new Dictionary<string, ClassInfo>();
        }
        public static ClassDeclarationCls Create(CreatorCls creator)
        {
            ClassDeclarationCls declaration = new ClassDeclarationCls(creator);
            try
            {
                declaration.LoadByReflection();

                // declaration.LoadFromFile();
            }
            catch (Exception exc)
            {
                creator.LogError(exc, "Chyba při načítání deklarace tříd: " + exc.Message);
            }
            return declaration;
        }


        /// <summary>
        /// Reference na objekt Creator, který zajistí přístup ke všem objektům
        /// </summary>
        public CreatorCls Creator { get; private set; }
        /// <summary>
        /// Reference na objekt Database, který zajistí přístup k SQL serveru
        /// </summary>
        public DatabaseCls Database { get { return this.Creator.Database; } }
        /// <summary>
        /// Soupis tříd a jejich vlastností
        /// </summary>
        public Dictionary<string, ClassInfo> Classes { get; set; }
        #endregion
        #region Získávání informací pomocí reflexe
        protected void LoadByReflection()
        {
            List<Type> recordClasses = this.Creator.FindTypes(type => type.IsDescendantOfType(typeof(RecordCls)));
            foreach (Type recordType in recordClasses)
                this.AddInfoForType(recordType);
        }
        protected void AddInfoForType(Type recordType)
        {
            ClassInfo classInfo = ClassInfo.CreateFromType(recordType, this);
            if (classInfo == null)
                this.Creator.LogError("Chyba v deklaraci třídy. Type: " + recordType.NamespaceName());
            else
                this.Classes.Add(classInfo.FullTypeName, classInfo);
        }
        #endregion
        #region Načítání z XML souboru
        protected void LoadFromFile()
        {
            string fileName = this.Creator.SearchDataFile(FILE_NAME);
            if (fileName != null)
                this.LoadFromFile(fileName);
        }
        public const string FILE_NAME = "ClassDeclaration.xml";
        /// <summary>
        /// Načte obsah daného souboru do this
        /// </summary>
        /// <param name="fileName"></param>
        protected void LoadFromFile(string fileName)
        {
            try
            {
                XDocument xDoc = XDocument.Load(fileName);
                if (xDoc.Root != null && xDoc.Root.Name.LocalName == "analytics")
                {
                    foreach (XElement element in xDoc.Root.Elements())
                        this.AddItemFrom(element);
                }
                this.LoadFinalize();
            }
            catch (Exception exc)
            {
                this.Creator.LogError(exc, "Chyba v XML souboru obsahujícího deklaraci třídy: " + fileName);
            }
        }
        /// <summary>
        /// Finalizuje záznamy o třídách - doplní data tříd do záznamů položek, sestaví reverzní soupisy Ownerů do tříd, které reprezentují položky
        /// </summary>
        private void LoadFinalize()
        {
            List<ClassInfo> classes = this.Classes.Values.ToList();

            // Do dat o položkách (ClassInfo.EntryList) doplním ClassInfo třídy položek:
            foreach (ClassInfo classInfo in classes)
            {
                foreach (EntryInfo entryInfo in classInfo.EntryList)
                {
                    if (String.IsNullOrEmpty(entryInfo.FullTypeName))
                    {
                        this.Creator.LogError("Položka %0 ve třídě %1 nemá vyplněn atribut FullTypeName.".FormatPercent(entryInfo.EntryName, classInfo.FullTypeName));
                    }
                    else
                    {
                        ClassInfo entryClass;
                        if (!this.Classes.TryGetValue(entryInfo.FullTypeName, out entryClass))
                            this.Creator.LogError("Položka %0 ve třídě %1 má vyplněn atribut FullTypeName = %2, takový typ není definován.".FormatPercent(entryInfo.EntryName, classInfo.FullTypeName, entryInfo.FullTypeName));
                        else
                        {
                            entryInfo.EntryClass = entryClass;
                            entryClass.OwnerClass = classInfo;
                        }
                    }
                }
            }

            // Kontrola, zda v deklaraci není uvedena některá třída jako položková, přičemž sama má další položky (rekurzivní položky nepovolujeme):
            foreach (ClassInfo classInfo in classes)
            {
                if (classInfo.HasEntries && classInfo.IsEntryClass)
                    this.Creator.LogError("Třída %0 je chybně deklarovaná: má definované položky (počet = %0), a přitom sama je položkovou třídou pro třídu %1.".FormatPercent(classInfo.EntryList.Count, classInfo.OwnerClass));
            }
        }
        /// <summary>
        /// Přidá do sebe další prvek z XML elementu.
        /// Automaticky detekuje jeho typ podle názvu elementu.
        /// </summary>
        /// <param name="element"></param>
        private void AddItemFrom(XElement element)
        {
            try
            {
                switch (element.Name.LocalName)
                {
                    case "class":
                        this.FillClassInfo(element);
                        break;
                    default:
                        this.Creator.LogError("Chyba v deklaraci XML elementu (neznámý název): " + element.ToString());
                        break;
                }
            }
            catch (Exception exc)
            {
                this.Creator.LogError(exc, "Chyba v deklaraci XML elementu: " + element.ToString());
            }
        }
        /// <summary>
        /// Přidá do sebe informace o jedné třídě popsané dodaným XML elementem.
        /// </summary>
        /// <param name="element"></param>
        private void FillClassInfo(XElement element)
        {
            ClassInfo classInfo = ClassInfo.CreateFromXml(element, this);
            if (classInfo == null)
                this.Creator.LogError("Chyba v deklaraci třídy. XML element: " + element.ToString());
            else
                this.Classes.Add(classInfo.FullTypeName, classInfo);
        }
        #endregion

        /// <summary>
        /// Vrátí informace o třídě daného typu.
        /// Pokud informace neexistuje, vyhodí chybu.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public ClassInfo GetClassInfo(Type type)
        {
            ClassInfo result = this.TryGetClassInfo(type);
            if (result == null)
                throw new NeuroDataNotFoundException("V dotazu na data třídy ClassDeclaration.GetClassInfo() je předán typ, který není uveden v deklaraci tříd: " + type.NamespaceName());
            return result;
        }
        /// <summary>
        /// Vrátí informace o třídě daného typu
        /// Pokud informace neexistuje, vrátí null.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public ClassInfo TryGetClassInfo(Type type)
        {
            if (type == null)
                throw new NeuroDataNotFoundException("V dotazu na data třídy ClassDeclaration.GetClassInfo() není předán Type.");
            string fullTypeName = type.NamespaceName();
            ClassInfo result;
            if (this.Classes.TryGetValue(fullTypeName, out result))
                return result;
            if (!type.IsDescendantOfType(typeof(RecordCls)))
                return null;
            
            this.AddInfoForType(type);
            if (this.Classes.TryGetValue(fullTypeName, out result))
                return result;
            
            return null;
        }
    }
    #endregion
    #region ClassInfo : Data o jedné datové třídě načtená z XML
    /// <summary>
    /// ClassInfo : Data o jedné datové třídě načtená z XML
    /// </summary>
    public class ClassInfo
    {
        #region Konstrukce
        public ClassInfo(ClassDeclarationCls classDeclaration)
        {
            this.ClassDeclaration = classDeclaration;
            this.EntryList = new List<EntryInfo>();
            this._InitId();
            this.FillDefaults();
        }
        private void FillDefaults()
        {
            this.FullTypeName = null;
            this.TypeNumber = 0;
            this._TableName = null;
            this._SqlSelect = null;
            this._ColumnIdName = null;
            this._ColumnSubIdName = null;
            this.ClassType = null;
        }
        public override string ToString()
        {
            return this.FullTypeName;
        }
        private ClassDeclarationCls ClassDeclaration;
        private string _TableName;
        private string _ColumnIdName;
        private string _ColumnSubIdName;
        private string _SqlSelect;
        #endregion
        #region Generování ClassInfo z Type
        public static ClassInfo CreateFromType(Type recordType, ClassDeclarationCls classDeclaration)
        {
            if (recordType == null) return null;
            ClassInfo classInfo = new ClassInfo(classDeclaration);
            RecordTableAttribute table = recordType.GetCustomAttributes(typeof(RecordTableAttribute), false).FirstOrDefault() as RecordTableAttribute;
            if (table == null) return null;
            RecordColumnIdAttribute columnId = recordType.GetCustomAttributes(typeof(RecordColumnIdAttribute), false).FirstOrDefault() as RecordColumnIdAttribute;
            RecordSelectAttribute select = recordType.GetCustomAttributes(typeof(RecordSelectAttribute), false).FirstOrDefault() as RecordSelectAttribute;
            RecordCommentAttribute comment = recordType.GetCustomAttributes(typeof(RecordCommentAttribute), false).FirstOrDefault() as RecordCommentAttribute;

            classInfo.FullTypeName = recordType.NamespaceName();
            classInfo.ClassType = recordType;
            classInfo.TableName = table.RecordTable;
            classInfo.ColumnIdName = (columnId == null ? null : columnId.ColumnId);
            //classInfo.ColumnSubIdName = element.GetAttributeString("ColumnSubIdName");
            classInfo.SqlSelect = (select == null ? null : select.RecordSelect);
            classInfo.Comment = (comment == null ? null : comment.Comment);

            // Data o položkách (položky jsou uloženy v instančních property, které implementují interface IEntries:
            IEnumerable<PropertyInfo> propertyInfos = recordType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(p => p.PropertyType.ImplementInterface(typeof(IEntries)));
            foreach (PropertyInfo propertyInfo in propertyInfos)
                classInfo.AddEntryFromProperty(propertyInfo);

            return classInfo;
        }

        private void AddEntryFromProperty(PropertyInfo propertyInfo)
        {
            EntryInfo entry = EntryInfo.CreateFromProperty(propertyInfo, this);
            if (entry != null)
                this.EntryList.Add(entry);
        }
        #endregion
        #region Generování ClassInfo z XML elementu
        public static ClassInfo CreateFromXml(XElement element, ClassDeclarationCls classDeclaration)
        {
            if (element == null) return null;
            if (element.Name.LocalName != "class") return null;
            ClassInfo classInfo = new ClassInfo(classDeclaration);
            classInfo.FullTypeName = element.GetAttributeString("FullTypeName");
            classInfo.TypeNumber = element.GetAttributeInt32("TypeNumber");
            classInfo.TableName = element.GetAttributeString("TableName");
            classInfo.ColumnIdName = element.GetAttributeString("ColumnIdName");
            classInfo.ColumnSubIdName = element.GetAttributeString("ColumnSubIdName");
            classInfo.SqlSelect = element.GetAttributeString("SqlSelect");
            classInfo.Comment = element.GetAttributeString("Comment");
            foreach (XElement subElement in element.Elements())
            {
                switch (subElement.Name.LocalName)
                {
                    case "entry":
                        classInfo.AddEntryFromXml(subElement);
                        break;
                }
            }
            classInfo.ClassType = Type.GetType(classInfo.FullTypeName, false, false);
            if (classInfo.ClassType == null)
                throw new FormatException("Deklarace třídy je chybná, nelze vytvořit odpovídající typ instance %0. (XML element: %1).".FormatPercent(classInfo.FullTypeName, element.ToString()));
            return classInfo;
        }
        private void AddEntryFromXml(XElement element)
        {
            EntryInfo entry = EntryInfo.CreateFromXml(element, this);
            if (entry != null)
                this.EntryList.Add(entry);
        }
        #endregion
        #region Property
        /// <summary>
        /// Reference na objekt Creator, který zajistí přístup ke všem objektům
        /// </summary>
        public CreatorCls Creator { get { return this.ClassDeclaration.Creator; } }
        /// <summary>
        /// Reference na objekt Database, který zajistí přístup k SQL serveru
        /// </summary>
        public DatabaseCls Database { get { return this.ClassDeclaration.Database; } }
        /// <summary>
        /// Plná název typu (Namespace.Name)
        /// </summary>
        public string FullTypeName { get; protected set; }
        /// <summary>
        /// Číslo typu, součást identifikace v databázi
        /// </summary>
        public int TypeNumber { get; protected set; }
        /// <summary>
        /// Název tabulky, v níž jsou ukládána data. Včetně vlastníka.
        /// </summary>
        public string TableName
        {
            get
            {
                if (!String.IsNullOrEmpty(this._TableName))
                    return this._TableName;
                return "";
            }
            protected set { this._TableName = value; }
        }
        /// <summary>
        /// Název sloupce obsahujícího ID záznamu.
        /// U položek tento sloupec obsahuje ID 
        /// </summary>
        public string ColumnIdName
        {
            get
            {
                if (!String.IsNullOrEmpty(this._ColumnIdName))
                    return this._ColumnIdName;
                return "id";
            }
            protected set { this._ColumnIdName = value; }
        }
        /// <summary>
        /// Název sloupce obsahujcího SubID záznamu = číslo položky v rámci ID mastera
        /// </summary>
        public string ColumnSubIdName
        {
            get
            {
                if (!String.IsNullOrEmpty(this._ColumnSubIdName))
                    return this._ColumnSubIdName;
                return "id2";
            }
            protected set { this._ColumnSubIdName = value; }
        }
        /// <summary>
        /// Vrací SQL dotaz pro načítání dat třídy.
        /// Může být null, pak obsahuje defaultní tvar = "SELECT * FROM (TableName)"
        /// </summary>
        public string SqlSelect
        {
            get
            {
                if (!String.IsNullOrEmpty(this._SqlSelect))
                    return this._SqlSelect;
                return "SELECT * FROM " + this.TableName;
            }
            protected set { this._SqlSelect = value; }
        }
        /// <summary>
        /// Libovolný komentář
        /// </summary>
        public string Comment { get; protected set; }
        /// <summary>
        /// Soupis informací o mých položkách (které položky patří do záznamu této třídy)
        /// </summary>
        public List<EntryInfo> EntryList { get; protected set; }
        /// <summary>
        /// Informace o třídě, která je Owner pro this třídu (která je položková)
        /// </summary>
        public ClassInfo OwnerClass { get; set; }
        /// <summary>
        /// Zde je uložen Type záznamů
        /// </summary>
        public Type ClassType { get; set; }
        /// <summary>
        /// Příznak, že toto je položková třída = má naplněnou Master třídu (this.MasterClass není null).
        /// Taková třída nemůže mít svoje vlastní položky (to by byly položky položek, a to se nepřipouští).
        /// </summary>
        public bool IsEntryClass { get { return (this.OwnerClass != null); } }
        /// <summary>
        /// Příznak, že toto je master třída = nemá naplněnou Master třídu (this.MasterClass je null).
        /// Taková třída může (ale nemusí) mít svoje vlastní položky.
        /// </summary>
        public bool IsMasterClass { get { return (this.OwnerClass == null); } }
        /// <summary>
        /// Příznak, že tato třída má položky (seznam this.EntryList má nějaké položky).
        /// Taková třída nesmí být sama položková (její IsEntryClass musí být false), jinak by šlo o položkovou třídu s položkami, což se nepřipouští.
        /// </summary>
        public bool HasEntries { get { return (this.EntryList.Count > 0); } }
        #endregion
        #region Generátor ID
        /// <summary>
        /// Resetuje ID.
        /// Volá se typicky po chybě při ukládání, kdy dojde k duplicitě.
        /// </summary>
        public void ResetId()
        {
            this._LastId = null;
        }
        protected Int32 GetNextId()
        {
            lock (this._NextIdLock)
            {
                if (!this._LastId.HasValue)
                    this._LastId = this._FindLastId();
                this._LastId = this._LastId.Value + 1;
                return this._LastId.Value;
            }
        }
        private void _InitId()
        {
            this._NextIdLock = new object();
            this._LastId = null;
        }
        private int _FindLastId()
        {
            string commandText = "SELECT top 1 " + this.ColumnIdName + " FROM " + this.TableName + " with (readuncommitted) ORDER BY " + this.ColumnIdName + " DESC";
            return this.Database.ReadValue<int>(commandText);
        }
        private object _NextIdLock;
        private Int32? _LastId;
        #endregion
        #region Podpora pro tvorbu instancí cílové třídy. Ukládání instance.
        /// <summary>
        /// Vytvoří a vrátí instanci datové třídy.
        /// Využívá bezparametrický konstruktor.
        /// Vrácený objekt nepřetypovává.
        /// </summary>
        /// <returns></returns>
        public object CreateInstance()
        {
            return System.Activator.CreateInstance(this.ClassType);
        }
        /// <summary>
        /// Vytvoří a vrátí instanci datové třídy.
        /// Využívá bezparametrický konstruktor.
        /// Vrácený objekt přetypuje na IRecord, nebo vrací null.
        /// </summary>
        /// <returns></returns>
        public IRecord CreateRecord()
        {
            return System.Activator.CreateInstance(this.ClassType) as IRecord;
        }
        /// <summary>
        /// Vytvoří a vrátí instanci datové třídy.
        /// Využívá bezparametrický konstruktor.
        /// Vrácený objekt se pokusí přetypovat na požadované generikum.
        /// </summary>
        /// <returns></returns>
        public T CreateTypeInstance<T>() where T : class
        {
            return System.Activator.CreateInstance(this.ClassType) as T;
        }
        /// <summary>
        /// Dodanou instanci uloží.
        /// </summary>
        /// <param name="data"></param>
        public void Save(IRecord data)
        {
            if (!data.GetType().Equals(this.ClassType))
                throw new NeuroSysException("Nelze ukládat data třídy " + data.GetType().NamespaceName() + " pomocí ClassInfo třídy " + this.ClassType.NamespaceName() + ".");

            this.SaveRecord(data);
            this.SaveEntries(data);
        }
        private void SaveRecord(IRecord data)
        {
            string sql = null;
            if (data.Id == 0)
            {
                data.Id = this.GetNextId();
                sql = this.GetSqlInsert(data);
            }
            else
            {
                sql = this.GetSqlUpdate(data);
            }
            this.Database.ExecuteCommand(sql);
        }
        private void SaveEntries(IRecord data)
        {
            foreach (EntryInfo entryInfo in this.EntryList)
            {
                IEntries entries = entryInfo.EntryPropertyInfo.GetValue(data, null) as IEntries;
                if (entries != null)
                {
                    this.SaveEntriesDelete(data, entries.ItemType, entries.EntriesToDelete);
                    this.SaveEntriesSave(data, entries.ItemType, entries.EntriesToSave);
                }
            }
        }
        private void SaveEntriesDelete(IRecord data, Type itemType, IEnumerable<IRecord> entries)
        {
            if (entries == null) return;
            // this.GetSqlDelete(entries.Select(r => r.Id));
        }
        private void SaveEntriesSave(IRecord data, Type itemType, IEnumerable<IRecord> entries)
        {
            if (entries == null) return;
            ClassInfo entryInfo = this.ClassDeclaration.GetClassInfo(itemType);
            foreach (IRecord entry in entries)
                entryInfo.Save(entry);
        }
        #endregion
        #region Tvorba SQL dotazů pro načítání a ukládání dat
        /// <summary>
        /// Vrátí jednoduchý SQL dotaz, který načte jednu větu s daným ID:
        /// SELECT * FROM (TableName) WHERE (ColumnIdName) = (id)
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public string GetSqlSelect(int id)
        {
            return this.SqlSelect + " WHERE " + this.ColumnIdName + " = " + id.ToString();
        }
        /// <summary>
        /// Vrátí sadu jednoduchých SQL dotazů, které načtou řadu záznamů pro řadu dodaných klíčů ID:
        /// SELECT * FROM (TableName) WHERE (ColumnIdName) in (id1, id2, ..., idnnn).
        /// Každý jednotlivý SQL příkaz má maximální délku 16KB, kvůli SQL serveru (aby nebyl dlouhý třeba 20MB, což SQL server nezvládne zpracovat :-( ! )
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public List<string> GetSqlSelects(IEnumerable<int> ids)
        {
            string sqlCommand = this.SqlSelect + " WHERE " + this.ColumnIdName + " in {{Items}}";
            List<string> sqlCommands = DatabaseCls.CreateSqlBatches(sqlCommand, "{{Items}}", ids, 16384);
            return sqlCommands;
        }
        /// <summary>
        /// Vytvoří a vrátí SQL příkaz pro INSERT záznamu této třídy
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string GetSqlInsert(IRecordSave data)
        {
            RecordSaveInfo saveInfo = new RecordSaveInfo();
            saveInfo.Add(this.ColumnIdName, data.Id);
            data.Save(saveInfo);
            return "INSERT INTO " + this.TableName + " " + saveInfo.SqlPhraseInsertColumns + " VALUES " + saveInfo.SqlPhraseInsertValues + "";
        }
        /// <summary>
        /// Vytvoří a vrátí SQL příkaz pro UPDATE záznamu této třídy.
        /// Pokud záznam neobsahuje změny (data.IsDirty je false), pak vrací null.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string GetSqlUpdate(IRecordSave data)
        {
            if (!data.IsDirty) return null;
            RecordSaveInfo saveInfo = new RecordSaveInfo();
            data.Save(saveInfo);
            saveInfo.RemoveIfExists(this.ColumnIdName);
            return "UPDATE " + this.TableName + " SET " + saveInfo.SqlPhraseUpdatePairs + " WHERE " + this.ColumnIdName + " = " + data.Id.ToString();
        }
        /// <summary>
        /// Vytvoří a vrátí SQL příkaz pro DELETE záznamu této třídy.
        /// Pokud záznam má nulové ID, pak vrací null.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string GetSqlDelete(IRecordSave data)
        {
            if (data.Id == 0) return null;
            return "DELETE FROM " + this.TableName + " WHERE " + this.ColumnIdName + " = " + data.Id.ToString();
        }
        #endregion
    }
    #endregion
    #region EntryInfo : Data o položkách jedné třídy
    /// <summary>
    /// EntryInfo : Data o položkách jedné třídy
    /// </summary>
    public class EntryInfo
    {
        public EntryInfo(ClassInfo ownerClass)
        {
            this.OwnerClass = ownerClass;
        }
        public override string ToString()
        {
            return this.EntryName + ": " + this.FullTypeName + " (Owner: " + this.OwnerClass.FullTypeName + ")";
        }
        public static EntryInfo CreateFromXml(XElement element, ClassInfo ownerClass)
        {
            if (element == null) return null;
            if (element.Name.LocalName != "entry") return null;
            EntryInfo entryInfo = new EntryInfo(ownerClass);
            entryInfo.EntryName = element.GetAttributeString("EntryName");
            entryInfo.FullTypeName = element.GetAttributeString("FullTypeName");
            entryInfo.BehaviorMode = element.GetAttributeEnum<EntriesBehaviorMode>("BehaviorMode", EntriesBehaviorMode.None);
            entryInfo.Comment = element.GetAttributeString("Comment");
            return entryInfo;
        }
        public static EntryInfo CreateFromProperty(PropertyInfo propertyInfo, ClassInfo ownerClass)
        {
            if (propertyInfo == null) return null;
            EntryInfo entryInfo = new EntryInfo(ownerClass);
            //entryInfo.EntryName = element.GetAttributeString("EntryName");

            EntriesBehaviorAttribute behavior = propertyInfo.GetCustomAttributes(typeof(EntriesBehaviorAttribute), false).FirstOrDefault() as EntriesBehaviorAttribute;
            RecordCommentAttribute comment = propertyInfo.GetCustomAttributes(typeof(RecordCommentAttribute), false).FirstOrDefault() as RecordCommentAttribute;
            entryInfo.EntryPropertyInfo = propertyInfo;
            entryInfo.EntryType = propertyInfo.PropertyType.GetGenericArguments().FirstOrDefault();
            entryInfo.BehaviorMode = (behavior == null ? EntriesBehaviorMode.None : behavior.BehaviorMode);
            entryInfo.Comment = (comment == null ? null : comment.Comment);
            return entryInfo;
        }
        /// <summary>
        /// Třída vlastníka položky.
        /// Zásadní vlastností položky je to, že patří jen jednomu vlastníkovi.
        /// Pokud by existovala třída, která spadá pod víc vlastníků, pak nejde o položku, ale o záznamy vztažené k jednomu záznamu.
        /// </summary>
        public ClassInfo OwnerClass { get; protected set; }
        /// <summary>
        /// Název položky, klíč odlišující v rámci jednoho Ownera více typů položek.
        /// Typicky může mít třída jednak položky typu Synapsis, a druhak položky typu Sequence.
        /// </summary>
        public string EntryName { get; protected set; }
        /// <summary>
        /// Třída těchto položek (informace o datové třídě položek)
        /// </summary>
        public ClassInfo EntryClass { get; set; }
        /// <summary>
        /// Informace o property zznamu Master, v němž jsou uloženy tyto položky.
        /// </summary>
        public PropertyInfo EntryPropertyInfo { get; set; }
        /// <summary>
        /// Type těchto položek
        /// </summary>
        public Type EntryType { get; set; }
        /// <summary>
        /// Název třídy těchto položek (Namespace.Name)
        /// </summary>
        public string FullTypeName { get; protected set; }
        /// <summary>
        /// Režim načítání těchto položek.
        /// Pokud je Immediately, pak se data položek načítají současně s daty Ownera (tedy ihned po jeho načtení). 
        /// Když se načítá celý seznam záznamů Owner, pak se jejich položky načítají ihned po načtení poslední položky, dalším SQL dotazem.
        /// Pokud je LoadType == Lazy, pak se položky načítají při prvním {get} na seznam položek, což může vést k zpomalení systému, pokud bude takových ownerů mnoho.
        /// </summary>
        public EntriesBehaviorMode BehaviorMode { get; protected set; }
        /// <summary>
        /// Komentář
        /// </summary>
        public string Comment { get; protected set; }

        #region Přístup k datům objektu přes PropertyInfo
        /// <summary>
        /// Vrátí IEntries z daného objektu, odpovídající this položkám
        /// </summary>
        /// <param name="record"></param>
        /// <returns></returns>
        public IEntries GetIEntries(IRecord record)
        {
            return (this.EntryPropertyInfo == null ? null : (this.EntryPropertyInfo.GetValue(record, null) as IEntries));
        }

        #endregion

    }
    public enum ItemLoadMode
    {
        None,
        Immediately,
        Lazy
    }
    #endregion
}
